

public class StackPlot {

  int numOfCategories;
  int[] categoryColors;
  boolean[] categoryIncluded;
  int catToRemove;
  boolean removingCategory;
  int catToAdd;
  boolean addingCategory;

  Integrator[][] freqPerCat;  // freqPerCat[ magCategory][ month] hold cum freq
  float[][] targetValues;
  int[][] freqArray;// hold frew
 
  float freqMax;
  float freqMin;
  float targetFreqMax;
  float targetFreqMin;
  Integrator dynFreqMax;
  Integrator dynFreqMin;

  Date dateMax;
  Date dateMin;
  Date targetDateMax;
  Date targetDateMin;
  Date initDateMax;
  Date initDateMin;
  Integrator dynDateMax;
  Integrator dynDateMin;

  float plotX1, plotY1; //upper left
  float plotX2, plotY2; //lower right
  float lableY;
  
  Integrator dynPlotAreaX2;
  boolean minimized;

  PFont plotFont;
  String title;
  String xAxisTitle;
  String yAxisTitle;
  float titleSize;
  float axisTitleSize;
  float lableSize;
  boolean showAxisLabels;
  float xAxisMajorInterval;
  float freqMajorInterval;
  float freqMinorInterval;
  
  boolean targetingNewDates;
  boolean targetingNewFreqs;
  
  float majorInterval;
  float minorInterval;
 
  StackPlot(int[][] freqArray, Date dateMin, Date dateMax, float plotX1, float plotY1, float plotX2, float plotY2, String title,
             String xAxisTitle, String yAxisTitle, int[] categoryColors ) {
    
    freqMax = initFreqPerCat(freqArray);  
    freqMin = 0;
    dynFreqMax = new Integrator( (float) freqMax );
    dynFreqMin = new Integrator( (float) freqMin );   
    numOfCategories = freqPerCat.length;
    this.freqArray = freqArray;
    
    this.dateMin = dateMin;
    initDateMin = new Date( dateMin.theMonth, dateMin.theYear);
    if( dateMax.theYear > 2010 ){
      dateMax.changeYear(2010);
      dateMax.changeMonth(6);
    }// else{
      this.dateMax = dateMax;
      initDateMax = new Date( dateMax.theMonth, dateMax.theYear);
    //}
    this.plotX1 = plotX1;
    this.plotY1 = plotY1;
    this.plotX2 = plotX2;
    this.plotY2 = plotY2;  
    this.title = title;
    this.xAxisTitle = xAxisTitle;
    this.yAxisTitle = yAxisTitle;
    
    titleSize = (plotY2-plotY1)/16; 
    axisTitleSize =  (plotY2-plotY1)/20;
    lableSize = axisTitleSize-1;
    
    textSize(axisTitleSize);
    lableY = plotX1 - textWidth(yAxisTitle) - 5;
    
    this.categoryColors = categoryColors;
    categoryIncluded = new boolean[numOfCategories];  //all categories included in the beginning
    for(int i = 0; i< numOfCategories; i++){
      categoryIncluded[i] = true;
    }
    
    dynDateMax = new Integrator( dateMax.theYear*12 + ((float) dateMax.theMonth-1.0) );
    dynDateMin = new Integrator( dateMin.theYear*12 + ((float) dateMin.theMonth-1.0) );
    
    showAxisLabels = true;
  
    targetingNewDates = false;
    targetingNewFreqs = false;
    
    initDateIntervals();
    
    PFont font = createFont("Sans Serif", 20);
    textFont(font);
    
    smooth();
    
    initFreqIntervals();
    
    dynPlotAreaX2 = new Integrator( plotX2 + 10);
    minimized = false;
  }
 
 
  void drawPlot() {    
    rectMode(CORNERS);
    drawBackground();
    
    if( dynPlotAreaX2.isTargeting()){
      dynPlotAreaX2.update();
    }
    //drawTitles();
    //drawAxisTitles();  
    //drawXAxisLables();
    //drawFreqLables();
    
    if( targetingNewDates ){
      dynDateMax.update();
      dynDateMin.update();
      //dynFreqMax.update();
      dateMax.theYear = (int) dynDateMax.value / 12;
      dateMax.theMonth = (int) dynDateMax.value % 12 + 1;
      dateMin.theYear = (int) dynDateMin.value / 12;
      dateMin.theMonth = (int) dynDateMin.value % 12 + 1;
      //freqMax = dynFreqMax.value;
      //boolean freqStillTargeting = updateFreqPerCat();
      
      if( !dynDateMax.isTargeting() && !dynDateMin.isTargeting()){ // && !freqStillTargeting ){
        targetingNewDates = false;
        targetingNewFreqs = false;
      }
        //initFreqIntervals(); 
       
    }//end targeting new dates
    else if( removingCategory || addingCategory){
      dynFreqMax.update();
      freqMax = dynFreqMax.value;
      
      boolean freqsStillTargeting = updateFreqPerCat();
      
      if( !dynFreqMax.isTargeting() && !freqsStillTargeting ){
        if( removingCategory ){
          categoryIncluded[catToRemove] = false;
          removingCategory = false;
          updateFreqPerCat();
        } else if( addingCategory ){
          categoryIncluded[catToAdd] = true;
          addingCategory = false; 
          updateFreqPerCat();
        }
        //refill freqPerCat with target values
        for(int i = 0; i < freqPerCat[0].length; i++){
          for(int j = 0; j < freqPerCat.length; j++){
            freqPerCat[j][i].value = targetValues[j][i];
            dynFreqMax.value = targetFreqMax;
          }
        }
        initFreqIntervals();
      }
    }//end removing or adding category
   
    if( (dateMax.theYear == dateMin.theYear && (abs(dateMax.theMonth -dateMin.theMonth) < 12 ))){
      
    } else if ( ((dateMax.theYear-dateMin.theYear) == 1)  && dateMax.theMonth <= 6 && dateMin.theMonth >= 6 ){
      
    } else if( !minimized && !dynPlotAreaX2.isTargeting()){ // don't draw if less than a month 
      drawTitles();
      drawAxisTitles();  
      drawXAxisLables();
      drawFreqLables();
      drawStacks();
    }
    
    rectMode(CORNER);
    
  }
 
  void drawTitles(){
    textAlign(LEFT);
    textSize(titleSize);

    fill(255);
    text(title, plotX1 , plotY1-(titleSize/2) );
  }
  
  void drawAxisTitles(){
    fill(255);
    textSize(axisTitleSize);
    textLeading(15);

    textAlign(CENTER, CENTER);

    text(yAxisTitle, lableY, (plotY1 + plotY2)/2);
    text(xAxisTitle, (plotX1+plotX2)/2, plotY2+23);
  }
  
  void drawXAxisLables() {
    if( targetingNewDates ){
      return;
    }
    fill(255);
    textSize(lableSize);
    textAlign(CENTER, TOP);
    stroke(128);
    strokeWeight(1);
    
    //do the first year
    for(int m = dateMin.theMonth; m <=12; m++ ){
      if( (m-1)%12 == 0 && dateMin.theYear% majorInterval == 0){
        float lowerBound = dateMin.theYear*12 + dateMin.theMonth;
        float upperBound = dateMax.theYear*12 + dateMax.theMonth;
        float value = dateMin.theYear*12+m;
        float x = map( value, lowerBound, upperBound, plotX1, plotX2);
        text( dateMin.theYear, x, plotY2+3);
        line(x, plotY1, x, plotY2);
      }
    }
    
    //do the second to the second-to-last year
    for(int y = ((int) dateMin.theYear+1); y <= ((int) dateMax.theYear-1); y++){
      for( int m = 1; m <= (int) 12; m++ ){
        if( (m-1) % 12 == 0  && (y % xAxisMajorInterval) == 0){//if january
          float lowerBound = dateMin.theYear*12 + dateMin.theMonth;
          float upperBound = dateMax.theYear*12 + dateMax.theMonth;
          float value = y*12+m;
          float x = map( value, lowerBound, upperBound, plotX1, plotX2);
          text( y , x, plotY2+3);
          line(x, plotY1, x, plotY2);
        }
      }
    }
    
    //do the last year
    for(int m = 1; m <= dateMax.theMonth; m++ ){
      if( (m-1)%12 == 0 && dateMax.theYear % xAxisMajorInterval == 0){
        float lowerBound = dateMin.theYear*12 + dateMin.theMonth;
        float upperBound = dateMax.theYear*12 + dateMax.theMonth;
        float value = dateMax.theYear*12+m;
        float x = map( value, lowerBound, upperBound, plotX1, plotX2);
        text( dateMax.theYear, x, plotY2+3);
        line(x, plotY1, x, plotY2);
      }
    }
  }//end method
  
  void drawFreqLables() {
    if( targetingNewFreqs || targetingNewDates || removingCategory || addingCategory){
      return;
    }
    fill(255);
    textSize(lableSize);
    stroke(128);
    strokeWeight(1);

    for( float v = freqMin; v <= freqMax; v += freqMinorInterval ) {
      float y = map( v, freqMin, freqMax, plotY2, plotY1 );
      if( v % freqMajorInterval == 0 ) { //major tick mark every 100 gallons
        if( v == 0 ) {
          textAlign(RIGHT);
        } 
        else if( v == freqMax ) {
          textAlign(RIGHT, TOP);
        } 
        else {
          textAlign(RIGHT, CENTER);
        }
        text( floor(v), plotX1-10, y);
        line( plotX1-4, y, plotX1, y);//major tick mark
      } 
      else {
        line( plotX1 - 2, y, plotX1, y);
      }
    }
  }
  
  void initFreqIntervals(){
     if( freqMax > 2000 ){
      freqMajorInterval = 500;
      freqMinorInterval = 250;
    } else if( freqMax > 500 ){
      freqMajorInterval = 200;
      freqMinorInterval = 100;
    } else if( freqMax > 100 ){
      freqMajorInterval = 50;
      freqMinorInterval = 25;
    } else if( freqMax > 50){
      freqMajorInterval = 10;
      freqMinorInterval = 5;
    } else if( freqMax > 10 ){
      freqMajorInterval = 5;
      freqMinorInterval = 1;
    } else{
      freqMajorInterval = 1;
      freqMinorInterval = 1;
    }
  }
  
  void initDateIntervals(){
    if( dateMax.theYear - dateMin.theYear > 20 ){
      xAxisMajorInterval = 5;
    } else if( dateMax.theYear - dateMin.theYear > 10 ){
      xAxisMajorInterval = 2;
    } else if( dateMax.theYear - dateMin.theYear > 5 ) {
      xAxisMajorInterval = 1;
    }
  }
  
  float initFreqPerCat(int[][] freqArray){
    freqPerCat = new Integrator[ freqArray.length ][ freqArray[0].length ];
    targetValues = new float[freqArray.length][freqArray[0].length];
    float runningMax = 0; //will not have max freq of 0
    for( int month = 0; month < freqPerCat[0].length; month++ ){
      float freqCount = 0;
      for( int category = 0; category < freqPerCat.length; category++){
        freqCount += freqArray[category][month];
        freqPerCat[category][month] = new Integrator( (float) freqCount );
      }
      if( freqCount > runningMax ){
        runningMax = freqCount; 
      }
    }//end outer for loop
    return runningMax; 
  }

  
  //no params, used when a cat has been added or removed
  float recalcFreqAfterCatRemoval(){
    float runningMax = 0; //will not have max freq < 0
    int startMonth =  (dateMin.theYear*12 + dateMin.theMonth) - (initDateMin.theYear*12+initDateMin.theMonth);
    int finishMonth =  ((dateMax.theYear)*12 + dateMax.theMonth) - (initDateMin.theYear*12+initDateMin.theMonth) + 1;
    //println("start month " + startMonth + " finish month " + finishMonth);
    for(int month = startMonth; month <= finishMonth; month++ ){
      float freqCount = 0;
      for( int category = 0; category < freqPerCat.length; category++){
        if( category == catToRemove ){
          //println("remove me: " + category);
          freqCount += 0;
           if( freqCount < 0 ){
            //println("freqCount < 0 for " + category + " count = " + freqCount);
            //println("target freq count is negative= " + freqCount);
          } 
          
          targetValues[category][month] = (float) freqCount;
          freqPerCat[category][month].target( (float) freqCount);
        }else if( categoryIncluded[category] ){
          freqCount += freqArray[category][month];
          if( freqCount < 0 ){
            //println("freqCount < 0 for " + category + " count = " + freqCount);
            //println("target freq count is negative= " + freqCount);
          } 
          targetValues[category][month] = (float) freqCount;
          freqPerCat[category][month].target( (float) freqCount );
        } else { //formerly removed
          freqCount += 0;// keep the same
          if( freqCount < 0 ){
            //println("freqCount < 0 for " + category + " count = " + freqCount);
            //println("target freq count is negative = " + freqCount);
          } 
          targetValues[category][month] = (float) freqCount;
          freqPerCat[category][month].target((float) freqCount );
        }
        //println( "freqPerCat , category = " + category + " month " + month + " = " + freqPerCat[category][month].value);
      }//end category for loop 
      //println(" -------- " );
      if( freqCount > runningMax ){
        runningMax = freqCount; 
      }
    }//end month for loop
    if( runningMax < 5 ){
      runningMax = 5;
    } else if( runningMax < 10 ){
      runningMax = 10;
    }
    return runningMax;
    
  }
  
  //no params, used when a cat has been added or removed
  float recalcFreqAfterCatAddition(){
    float runningMax = 0; //will not have max freq < 0
    int startMonth =  (dateMin.theYear*12 + dateMin.theMonth) - (initDateMin.theYear*12+initDateMin.theMonth);
    int finishMonth =  ((dateMax.theYear)*12 + dateMax.theMonth) - (initDateMin.theYear*12+initDateMin.theMonth) + 1;
    for(int month = startMonth; month <= finishMonth; month++ ){
      float freqCount = 0;
      for( int category = 0; category < freqPerCat.length; category++){
        if( category == catToAdd ){
          //println("add me: " + category);
          freqCount += freqArray[category][month];
          targetValues[category][month] = (float) freqCount;
          freqPerCat[category][month].target( (float) freqCount);
        }else if( categoryIncluded[category] ){
          freqCount += freqArray[category][month];
          targetValues[category][month] = (float) freqCount;
          freqPerCat[category][month].target( (float) freqCount );
        } else { //formerly removed
          freqCount += 0;// keep the same
          targetValues[category][month] = (float) freqCount;
          freqPerCat[category][month].target((float) freqCount );
        }
      }//end category for loop 
      if( freqCount > runningMax ){
        runningMax = freqCount; 
      }
    }//end month for loop
    if( runningMax < 5 ){
      runningMax = 5;
    } else if( runningMax < 10 ){
      runningMax = 10;
    }
    return runningMax;
  }

  
  boolean updateFreqPerCat( ){
    boolean targetCount = false;
    int startMonth =  (dateMin.theYear*12 + dateMin.theMonth) - (initDateMin.theYear*12+initDateMin.theMonth);
    int finishMonth = ((dateMax.theYear)*12 + dateMax.theMonth) - (initDateMin.theYear*12+initDateMin.theMonth) + 1;
    for(int month = startMonth; month <= finishMonth; month++ ){
      for( int category = 0; category < freqPerCat.length; category++){
        freqPerCat[category][month].update();
        targetCount = targetCount || freqPerCat[category][month].isTargeting(); //once all of them no longer targeting, will become false
      }
    }
    
    return targetCount;
  }
  
 void drawStacks(){
   for( int category = numOfCategories - 1; category >= 0; category--){ //for each category
     if( categoryIncluded[category] ){
       fill( categoryColors[category] );
       //noFill();
       noStroke();
       beginShape();
       //vertex(plotX1, plotY2);
       //do the first year
       //println("first year =  " + dateMin.theYear + " month = "+ dateMin.theMonth);
       int countMonth =  (dateMin.theYear*12 + dateMin.theMonth) - (initDateMin.theYear*12+initDateMin.theMonth);
       //println("start at " + countMonth);
       if( countMonth < 0){
         countMonth = 0;
       }
       for(int m = dateMin.theMonth; m <=12; m++ ){
          float lowerBound = dateMin.theYear*12 + dateMin.theMonth;
          float upperBound = dateMax.theYear*12 + dateMax.theMonth;
          float date = dateMin.theYear*12+m;
          float x = map( date, lowerBound, upperBound, plotX1, plotX2);
          float freq = freqPerCat[category][countMonth].value;
          if( freq < 0){
            if( freqPerCat[category][countMonth].isTargeting() ){
              //println( "freq < 0 ! while targeting " + countMonth + " " + category + " " + freqPerCat[category][countMonth].value );
            } else {
              //println( "freq < 0 ! while stable " + countMonth + " " + category + " " + freqPerCat[category][countMonth].value );
            }
          }
          float y = map(freq, freqMin, freqMax, plotY2, plotY1);
          vertex(x, y);
          countMonth++;
       }
      
      //do the second to the second-to-last year
       for(int y = ((int) dateMin.theYear+1); y <= ((int) dateMax.theYear-1); y++){
         for( int m = 1; m <= (int) 12; m++ ){
          float lowerBound = dateMin.theYear*12 + dateMin.theMonth;
          float upperBound = dateMax.theYear*12 + dateMax.theMonth;
          float date = y*12+m;
          float x = map( date, lowerBound, upperBound, plotX1, plotX2);
          float freq = freqPerCat[category][countMonth].value;
          float yCoord = map(freq, freqMin, freqMax, plotY2, plotY1);
          vertex(x, yCoord);
          countMonth++;
          //print if
          if( freqPerCat[category][countMonth].value  < 0 ){
            if( freqPerCat[category][countMonth].isTargeting() ){
              //println( "freq < 0 ! while targeting " + date + ",  " + category + " " + freqPerCat[category][countMonth].value );
            } else {
              //println( "freq < 0 ! while stable " + date + ",  " + category + " " + freqPerCat[category][countMonth].value );
            }
          }//end print if
        }
      }
 
     //println("last year =  " + dateMax.theYear + " month = "+ dateMax.theMonth);
     //println("end at " + countMonth);
      //do the last year
      for(int m = 1; m <= dateMax.theMonth; m++ ){
        float lowerBound = dateMin.theYear*12 + dateMin.theMonth;
        float upperBound = dateMax.theYear*12 + dateMax.theMonth;
        float date = dateMax.theYear*12+m;
        float x = map( date, lowerBound, upperBound, plotX1, plotX2);
        if( countMonth == freqPerCat[category].length-1){
          countMonth = freqPerCat[category].length-2;
        }
        float freq = freqPerCat[category][countMonth].value;
        float y = map(freq, freqMin, freqMax, plotY2, plotY1);
        vertex(x, y);
        countMonth++;
      }
    

      //draw lower left and lower right corners
      //vertex(map(dynRowMax.value, dynRowMin.value-1, dynRowMax.value, plotX1, plotX2)+1, plotY2);
      vertex(plotX2, plotY2);
      vertex(plotX1, plotY2);
      endShape(CLOSE);
     }//end if
   }//end category for loop
 }//end method
 
 void changeDateRange(Date newMinDate, Date newMaxDate){
   
   targetingNewDates = true;
   //targetingNewFreqs = true;
   if( abs( newMinDate.theMonth - dateMin.theMonth ) > 1 ){
      dynDateMin.target( (float) newMinDate.theYear*12 + newMinDate.theMonth-1 );
      targetDateMin = newMinDate;
   } else {
     targetDateMin = dateMin;
   }
   if( abs( newMaxDate.theMonth - dateMax.theMonth ) > 1 ){
     if( newMaxDate.theYear > 2010 ){
        newMaxDate.changeYear(2010);
        newMaxDate.changeMonth(6);
     }
     dynDateMax.target( (float) newMaxDate.theYear*12 + newMaxDate.theMonth-1 );
     targetDateMax = newMaxDate;
   } else{
     targetDateMax = dateMax;
   }
   
   //targetFreqMax = recalcMaxFreq(targetDateMin, targetDateMax);  
   
   //dynFreqMax.target( targetFreqMax );
 }
 
 void removeCategory(int catToRemove){
   removingCategory = true;
   this.catToRemove = catToRemove;
   
   targetFreqMax = recalcFreqAfterCatRemoval();
   dynFreqMax.target( targetFreqMax );
 }
 
 void addCategory(int catToAdd){
   addingCategory = true;
   this.catToAdd = catToAdd;
   
   targetFreqMax = recalcFreqAfterCatAddition();
   dynFreqMax.target( targetFreqMax );
 }

 void drawBackground(){
   fill(100, 100, 100, 125);
   rect( plotX1-100, plotY1-40, dynPlotAreaX2.value, plotY2+40 );
   
   fill(255);
   //boolean mouseIsOver = abs(mouseX - textX) < 20 && abs(mouseY - textY) < 20;
   //if( minimized ){
   //}
   float textX = dynPlotAreaX2.value - 40;
   float textY = plotY1-40 + 20;
   if( abs(mouseX - textX) < 20 && abs(mouseY - textY) < 20){
     textSize(15);
   } else {
     textSize(12);
   }
   textAlign(CENTER, CENTER);
   if( minimized ){
     text("show plot", textX, textY);
   } else{
     text("hide plot", textX, textY);
   }
 }
 
 void mousePressedCheck(int mX, int mY){
   if( abs(mouseX - (dynPlotAreaX2.value - 40)) < 20 && abs(mouseY - (plotY1-40 + 20)) < 20) {
     minimized = !minimized;
     textSize(15);
     if( minimized ){
       dynPlotAreaX2.target(plotX1-90+textWidth("show plot"));
     } else{
       dynPlotAreaX2.target(plotX2 + 10);
     }
   }
 }
 
}//end class  


